/*
*
==============================================================================
*  Name        : 
*  Part of     : Omen Game Engine - DirectX 10 Plugin
*  Description : 
*  Version     : 1.0
*
*  Copyright (c) 2013 Lauri Kortevaara.
* 
==============================================================================
*/

#define DIRECTX_10_PLUGIN_SOURCE 1

#include <OmenEngine>

#include <OmenMouseEvent.h>
#include <OmenKeyboardEvent.h>

#include "DirectX_10_InputDriver.h"
#include "DirectX_10_Plugin.h"

#include <iostream>

using namespace Omen;

DirectX_10_InputDriver::DirectX_10_InputDriver()
{
    m_name = L"DirectX_10_InputDriver";

    m_directInput = NULL;
    m_pMouseInputDevice = NULL;
    m_pKeyboardInputDevice = NULL;
}

DirectX_10_InputDriver::DirectX_10_InputDriver(const DirectX_10_InputDriver& other)
{
    m_directInput = other.m_directInput;
    m_devices = other.m_devices;
}

DirectX_10_InputDriver::~DirectX_10_InputDriver()
{
}

 /// <summary></summary>
const std::wstring& DirectX_10_InputDriver::getName() const
{
    return m_name;
}

 /// <summary></summary>
unsigned int DirectX_10_InputDriver::deviceCount() const
{
    return m_devices.size(); // Mouse and keyboard devices
}

BOOL DirectX_10_InputDriver::DIEnumDevicesCallback( LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef )
{
    if( lpddi && pvRef )
    {
        DirectX_10_InputDriver* pDriver = reinterpret_cast<DirectX_10_InputDriver*>(pvRef);
        if( pDriver && ((lpddi->dwDevType & 0xff) == DI8DEVTYPE_MOUSE) )
        {
            std::wcout << L"DirectX_10_InputDriver: Found Mouse input device!" << std::endl;
            pDriver->createMouseInputDevice(lpddi->guidInstance);
        }
        else
        if( pDriver && ((lpddi->dwDevType & 0xff) == DI8DEVTYPE_KEYBOARD) )
        {
            std::wcout << L"DirectX_10_InputDriver:: Found Keyboard input device!" << std::endl;
            pDriver->createKeyboardInputDevice(lpddi->guidInstance);
        }
    }
    return TRUE;
}

 /// <summary>Creates the input device using the driver's API</summary>
std::vector<IInputDevice*> DirectX_10_InputDriver::createInputDevices(unsigned long windowHandle)
{
    HWND hwnd = reinterpret_cast<HWND>(windowHandle);
    m_hwnd = hwnd;

    HRESULT result = S_OK;
    HINSTANCE hinstance = GetModuleHandle(NULL);

    // Initialize the main direct input interface.
    result = DirectInput8Create(hinstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_directInput, NULL);
    if(FAILED(result))
    {
        throw "Unable to create devices for DirectX_10_InputDriver!";
    }

    std::wcout << "DirectX_10_InputDriver:: Starting to enumerate input devices!" << std::endl;
    // Enumerate and create input device instances
    m_directInput->EnumDevices(DI8DEVCLASS_ALL, (LPDIENUMDEVICESCALLBACKW)DirectX_10_InputDriver::DIEnumDevicesCallback, this, DIEDFL_ALLDEVICES );

    return m_devices;
}

void DirectX_10_InputDriver::createMouseInputDevice( const GUID& guid )
{
    if( !m_pMouseInputDevice )
    {
        m_pMouseInputDevice = new MouseInputDevice(m_hwnd, guid, m_directInput);
        if( m_pMouseInputDevice )
        {
            m_devices.push_back(m_pMouseInputDevice);
        }
    }
}

void DirectX_10_InputDriver::createKeyboardInputDevice( const GUID& guid )
{
    if( !m_pKeyboardInputDevice )
    {
        m_pKeyboardInputDevice = new KeyboardInputDevice(m_hwnd, guid, m_directInput);
        m_devices.push_back(m_pKeyboardInputDevice);
    }

}

MouseInputDevice::MouseInputDevice(HWND hWnd, const GUID& guid, IDirectInput8* pDirectInput)
{
    m_hwnd = hWnd;
    m_mouse = NULL;

    m_mouseX = m_mouseY = m_mouseZ =  0;
    
    RECT windowRect;
    GetWindowRect( hWnd, &windowRect );
    m_screenWidth = windowRect.right - windowRect.left;
    m_screenHeight = windowRect.bottom - windowRect.top;

    // Initialize the direct input interface for the mouse.
    HRESULT result = pDirectInput->CreateDevice(guid, &m_mouse, NULL);
    if(FAILED(result))
    {
        throw std::runtime_error("Unable to create mouse input device for DirectX_10_InputDriver! Error in CreateDevice.");
    }

    // Set the data format for the mouse using the pre-defined mouse data format.
    result = m_mouse->SetDataFormat(&c_dfDIMouse);
    if(FAILED(result))
    {
        throw std::runtime_error("Unable to create mouse input device for DirectX_10_InputDriver! Error in SetDataFormat.");
    }

    //We use non-exclusive cooperative settings for the mouse. We will have to check for when it goes in and out of focus and re-acquire it each time.

    // Set the cooperative level of the mouse to share with other programs.
    result = m_mouse->SetCooperativeLevel(hWnd, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE );
    if(FAILED(result))
    {
        throw std::runtime_error("Unable to create mouse input device for DirectX_10_InputDriver! Error in SetCooperativeLevel.");
    }

    //Once the mouse is setup we acquire it so that we can begin using it.

    // Acquire the mouse.
    result = m_mouse->Acquire();
    if(FAILED(result))
    {
        throw std::runtime_error("Unable to create mouse input device for DirectX_10_InputDriver! Error in Acquire.");
    }
    std::wcout << L"DirectX_10_Plugin: Mouse InputDevice created successfully!" << std::endl;
}

MouseInputDevice::~MouseInputDevice() 
{
}

bool MouseInputDevice::handleInput()
{
    HRESULT result;
    static int oldX = 0;
    static int oldY = 0;
    static int oldZ = 0; // The wheel position

    // Read the mouse device.
    result = m_mouse->GetDeviceState(sizeof(DIMOUSESTATE), (LPVOID)&m_mouseState);
    if(FAILED(result))
    {
        // If the mouse lost focus or was not acquired then try to get control back.
        if((result == DIERR_INPUTLOST) || (result == DIERR_NOTACQUIRED))
        {
            result = m_mouse->Acquire();
            if( result != S_OK )
                return false;
        }
        else
        {
            return false;
        }
    }

    static HWND oldMouseCapture = 0;
    BYTE b1 = (m_mouseState.rgbButtons[0] & 0x80) ? 1 << 0 : 0;
    BYTE b2 = (m_mouseState.rgbButtons[1] & 0x80) ? 1 << 1 : 0;
    BYTE b3 = (m_mouseState.rgbButtons[2] & 0x80) ? 1 << 2 : 0;
    BYTE b4 = (m_mouseState.rgbButtons[3] & 0x80) ? 1 << 3 : 0;

    BYTE buttonStates = b1+b2+b3+b4;
    static bool buttonPressed = false;

    if( b3 )
    {
        buttonPressed = true;
    }
    else
    if( buttonPressed )
    {

        oldMouseCapture = 0;
    }


    // Update the location of the mouse cursor based on the change of the mouse location during the frame.
    m_mouseX += m_mouseState.lX;
    m_mouseY += m_mouseState.lY;
    m_mouseZ = (abs(m_mouseState.lZ)<200 ? m_mouseState.lZ : 0);

    if( oldX != m_mouseX || oldY != m_mouseY )
    {
        MouseEvent* mouseEvent = new MouseEvent;
        mouseEvent->setMousePosition( MouseEvent::MousePosition(m_mouseX, m_mouseY) );
        mouseEvent->setButtonStates( buttonStates );

        GetOmenFactory().getKernel().sendEvent(mouseEvent);
    }

    if( m_mouseZ != 0 )
    {
        MouseWheelEvent* mouseEvent = new MouseWheelEvent;
        mouseEvent->setWheelDelta( m_mouseZ );

        GetOmenFactory().getKernel().sendEvent(mouseEvent);
    }

    oldX = m_mouseX;
    oldY = m_mouseY;
    oldY = m_mouseZ;
    return true;
}

KeyboardInputDevice::KeyboardInputDevice(HWND hWnd, const GUID& guid, IDirectInput8* pDirectInput)
{
    m_hwnd = hWnd;
    m_keyboard = NULL;

    // Initialize the direct input interface for the keyboard.
    HRESULT result = pDirectInput->CreateDevice(guid, &m_keyboard, NULL);
    if(FAILED(result))
    {
        throw std::runtime_error("Unable to create keyboard input device for DirectX_10_InputDriver! Error in CreateDevice.");
    }

    // Set the data format.  In this case since it is a keyboard we can use the predefined data format.
    result = m_keyboard->SetDataFormat(&c_dfDIKeyboard);
    if(FAILED(result))
    {
        throw std::runtime_error("Unable to create keyboard input device for DirectX_10_InputDriver! Error in SetDataFormat.");
    }

    // Set the cooperative level of the keyboard to not share with other programs.
    result = m_keyboard->SetCooperativeLevel(hWnd, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE);
    if(FAILED(result))
    {
        throw std::runtime_error("Unable to create keyboard input device for DirectX_10_InputDriver! Error in SetCooperativeLevel.");
    }

    DIPROPDWORD dipdw;
    dipdw.diph.dwSize = sizeof(DIPROPDWORD);
    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    dipdw.diph.dwObj = 0;
    dipdw.diph.dwHow = DIPH_DEVICE;
    dipdw.dwData = KEYBOARD_BUFFER_SIZE;

    if (m_keyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph) != DI_OK)
    {
    }
    
    // Now acquire the keyboard.
    do {
        result = m_keyboard->Acquire();
    } while( result != DI_OK );
    std::wcout << L"DirectX_10_Plugin: Keyboard InputDevice created successfully!" << std::endl;
}

KeyboardInputDevice::~KeyboardInputDevice() 
{
}

bool KeyboardInputDevice::handleInput()
{
    HRESULT result;

    BYTE keyboardStates[256];
    result = m_keyboard->GetDeviceState(sizeof(keyboardStates), (LPVOID) keyboardStates );
    

    DIDEVICEOBJECTDATA key_pressed[KEYBOARD_BUFFER_SIZE]; 
    DWORD dwItems;
    HRESULT hRes = DIERR_INPUTLOST;

    while (DI_OK != hRes)
    {
        hRes = m_keyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), key_pressed, &dwItems, 0);
        if (hRes != DI_OK)
        {
            m_keyboard->Acquire();
        }
    }
    if( dwItems == 0 )
        return false;

    bool keypressed = false;

    bool keyevent[KEYBOARD_BUFFER_SIZE];
    memset(  keyevent, false, sizeof( keyevent ) );
    memset(  m_keyboardState, 0, sizeof( m_keyboardState ) );

    if ((key_pressed[0].dwData & 0x80) ? 1 : 0) // only key-down events are reported
    { 
        keypressed = true;
        keyevent[key_pressed[0].dwOfs] = true;
        static HKL layout=GetKeyboardLayout(0);
        
        // Map Virtual ScanCode to Virtual Key
        UINT vk=MapVirtualKeyEx(key_pressed[0].dwOfs,MAPVK_VSC_TO_VK,layout);
        m_keyboardState[vk] = 1;
    }

    //if( keypressed )
    {
        Omen::KeyboardEvent* pKeyboardEvent = new Omen::KeyboardEvent(m_keyboardState[0]);
        pKeyboardEvent->setButtons((BYTE*)&m_keyboardState);
        
        // Now as we've got the factory, get the kernel pointer
        Omen::IKernel& pKernel = GetOmenFactory().getKernel();
        pKernel.sendEvent(pKeyboardEvent);
    }

    return true;
}

void DirectX_10_InputDriver::handleInput() const
{
    if( m_pMouseInputDevice )
        this->m_pMouseInputDevice->handleInput();
    
    /*if( m_pKeyboardInputDevice )
        this->m_pKeyboardInputDevice->handleInput();*/
}

/// EOF